﻿#pragma once

#include "jsonhpp/json.hpp"
#include "util/tuple_util.hpp"
#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

class RedisExecutor;
class RedisJson;

/**
 * 构造JSON访问Redis服务.
 */
class RedisJson {
  std::string cmd_;                ///< 命令
  nlohmann::json json_;            ///< JSON对象
  bool batch_{false};              ///< 批量
  std::int32_t mapping_index_{-1}; ///< mapping key

private:
  /**
   * 向JSON对象内设置参数属性.
   */
  class Pusher {
    nlohmann::json *json_ptr_{nullptr}; ///< JSON对象指针

  public:
    /**
     * 构造.
     *
     * \param json_ptr JSON对象指针
     */
    Pusher(nlohmann::json *json_ptr) { json_ptr_ = json_ptr; }
    /**
     * 仿函数.
     *
     * \param item 调用参数
     */
    template <typename ItemType> void operator()(const ItemType &item) {
      json_ptr_->push_back(item);
    }
  };

  friend class RedisExecutor;

public:
  /**
   * 构造.
   *
   * \param batch 批量
   */
  RedisJson(bool batch = false) { batch_ = batch; }
  /**
   * 设置调用参数.
   *
   * \param ...args 调用参数
   * \return RedisJson引用
   */
  template <typename... VarArgsT> auto Args(VarArgsT... args) -> RedisJson & {
    auto tuple_args = std::make_tuple(args...);
    kratos::util::tuple_foreach(tuple_args, Pusher(&json_["args"]));
    return *this;
  }
  /**
   * 设置调用{KEY:VALUE}.
   *
   * \param key 参数KEY
   * \param value 参数值
   * \return RedisJson引用
   */
  template <typename KeyT, typename ValueT>
  auto Mapping(const KeyT &key, const ValueT &value) -> RedisJson & {
    if (-1 == mapping_index_) {
      nlohmann::json mapping;
      mapping[key] = value;
      json_["args"].push_back(mapping);
      mapping_index_ = std::int32_t(json_["args"].size() - 1);
    } else {
      auto &mapping = json_["args"][mapping_index_];
      mapping[key] = value;
    }
    return *this;
  }
  /**
   * 设置调用KEY=VALUE.
   *
   * \param key 参数KEY
   * \param value 参数值
   * \return RedisJson引用
   */
  template <typename KeyT, typename ValueT>
  auto Kwargs(const KeyT &key, const ValueT &value) -> RedisJson & {
    json_["kwargs"][key] = value;
    return *this;
  }

private:
  /**
   * 设置Redis命令.
   *
   * \param cmd Redis命令
   * \return RedisJson引用
   */
  auto Command(const std::string &cmd) -> RedisJson & {
    json_["command"] = cmd;
    return *this;
  }
  /**
   * 获取JSON对象.
   */
  auto get_json() -> nlohmann::json & { return json_; }
};

/**
 * 执行Redis命令.
 */
class RedisExecutor {
  nlohmann::json json_; ///< JSON对象
  using RedisJsonPtr = std::shared_ptr<RedisJson>;
  using RedisJsonVector = std::vector<RedisJsonPtr>;
  RedisJsonPtr cmd_;              ///< 单条命令
  RedisJsonVector batch_cmd_vec_; ///< 合批（Pipeline)

public:
  /**
   * 构造.
   */
  RedisExecutor() {}
  /**
   * 析构.
   */
  ~RedisExecutor() {}
  /**
   * 输出Redis命令的JSON串
   */
  inline auto to_json() -> std::string {
    nlohmann::json json_obj;
    for (const auto &cmd : batch_cmd_vec_) {
      json_obj.emplace_back(cmd->get_json());
    }
    if (cmd_) {
      json_obj = cmd_->get_json();
    }
    return json_obj.dump(2);
  }
  /**
   * 创建单条命令.
   *
   * \param command 命令名
   * \return RedisJson引用
   */
  template <typename... VarArgs>
  inline auto Command(const std::string &command, VarArgs... args)
      -> RedisJson & {
    if (!batch_cmd_vec_.empty() || cmd_) {
      throw std::runtime_error("In batch mode or has unfinished command");
    }
    cmd_ = std::make_shared<RedisJson>(false);
    cmd_->Command(command).Args(std::forward<VarArgs>(args)...);
    return *cmd_;
  }
  /**
   * 创建一个合批命令.
   *
   * \param command 命令名
   * \return RedisJson引用
   */
  template <typename... VarArgs>
  inline auto Batch(const std::string &command, VarArgs... args)
      -> RedisJson & {
    if (cmd_) {
      throw std::runtime_error("In single mode");
    }
    auto cmd_ptr = std::make_shared<RedisJson>(true);
    cmd_ptr->Command(command).Args(std::forward<VarArgs>(args)...);
    batch_cmd_vec_.push_back(cmd_ptr);
    return *batch_cmd_vec_.back();
  }
  /**
   * 安全的执行Redis命令, 不抛出异常.
   *
   * \param prx 服务代理
   * \param [IN OUT] ret_json JSON对象
   * \return true调用成功, false调用失败
   */
  template <typename ProxyT>
  auto ExecuteSafe(ProxyT prx, nlohmann::json &ret_json) -> bool {
    if (!prx || !prx->isConnected()) {
      batch_cmd_vec_.clear();
      json_.clear();
      cmd_.reset();
      return false;
    }
    auto ret = false;
    if (!batch_cmd_vec_.empty()) {
      for (const auto &cmd : batch_cmd_vec_) {
        json_.emplace_back(cmd->get_json());
      }
      try {
        auto str_ptr = prx->DoCommand(json_.dump());
        if (!str_ptr) {
          return false;
        }
        ret_json = nlohmann::json::parse(*str_ptr);
      } catch (std::exception &) {
        ret = false;
      }
      batch_cmd_vec_.clear();
      json_.clear();
    }
    if (cmd_) {
      try {
        auto str_ptr = prx->DoCommand(cmd_->get_json().dump());
        if (!str_ptr) {
          return false;
        }
        ret_json = nlohmann::json::parse(*str_ptr);
      } catch (std::exception &) {
        ret = false;
      }
      json_.clear();
      cmd_.reset();
    }
    return ret;
  }
  /**
   * 安全的执行Redis命令, 不抛出异常.
   *
   * \param prx 服务代理
   * \param [IN OUT] ret_struct 序列化对象
   * \return true调用成功, false调用失败
   */
  template <typename StructT, typename ProxyT>
  auto ExecuteSafe(ProxyT prx, StructT &ret_struct) -> bool {
    if (!prx || !prx->isConnected()) {
      batch_cmd_vec_.clear();
      json_.clear();
      cmd_.reset();
      return false;
    }
    auto ret = false;
    if (!batch_cmd_vec_.empty()) {
      for (const auto &cmd : batch_cmd_vec_) {
        json_.emplace_back(cmd->get_json());
      }
      try {
        auto str_ptr = prx->DoCommand(json_.dump());
        if (!str_ptr) {
          return false;
        }
        ret_struct.from_json(*str_ptr);
      } catch (std::exception &) {
        ret = false;
      }
      batch_cmd_vec_.clear();
      json_.clear();
    }
    if (cmd_) {
      try {
        auto str_ptr = prx->DoCommand(cmd_->get_json().dump());
        if (!str_ptr) {
          return false;
        }
        ret_struct.from_json(*str_ptr);
      } catch (std::exception &) {
        ret = false;
      }
      json_.clear();
      cmd_.reset();
    }
    return ret;
  }
  /**
   * 安全的执行Redis命令, 可能抛出异常.
   *
   * \param prx 服务代理
   * \return JSON对象
   */
  template <typename ProxyT>
  auto Execute(ProxyT prx) noexcept(false) -> nlohmann::json {
    if (!prx || !prx->isConnected()) {
      batch_cmd_vec_.clear();
      json_.clear();
      cmd_.reset();
      nlohmann::json ret_json;
      ret_json["result"] = "";
      ret_json["error"] = "Proxy not ready";
      return ret_json;
    }
    nlohmann::json ret_json;
    if (!batch_cmd_vec_.empty()) {
      for (const auto &cmd : batch_cmd_vec_) {
        json_.emplace_back(cmd->get_json());
      }
      try {
        auto str_ptr = prx->DoCommand(json_.dump());
        if (!str_ptr) {
          return nlohmann::json();
        }
        ret_json = nlohmann::json::parse(*str_ptr);
      } catch (std::exception & e) {
        batch_cmd_vec_.clear();
        json_.clear();
        throw std::runtime_error(e.what());
      }
      batch_cmd_vec_.clear();
      json_.clear();
    }
    if (cmd_) {
      try {
        auto str_ptr = prx->DoCommand(cmd_->get_json().dump());
        if (!str_ptr) {
          return nlohmann::json();
        }
        ret_json = nlohmann::json::parse(*str_ptr);
      } catch (std::exception & e) {
        json_.clear();
        cmd_.reset();
        throw std::runtime_error(e.what());
      }
      json_.clear();
      cmd_.reset();
    }
    return ret_json;
  }
  /**
   * 安全的执行Redis命令, 可能抛出异常.
   *
   * \param prx 服务代理
   * \param [IN OUT] ret_struct 序列化对象
   * \return JSON对象
   */
  template <typename StructT, typename ProxyT>
  auto Execute(ProxyT prx) noexcept(false) -> StructT {
    if (!prx || !prx->isConnected()) {
      batch_cmd_vec_.clear();
      json_.clear();
      cmd_.reset();
      nlohmann::json ret_json;
      ret_json["result"] = "";
      ret_json["error"] = "Proxy not ready";
      StructT ret_struct;
      ret_struct.from_json(ret_json.dump());
      return ret_struct;
    }
    StructT ret_struct;
    if (!batch_cmd_vec_.empty()) {
      for (const auto &cmd : batch_cmd_vec_) {
        json_.emplace_back(cmd->get_json());
      }
      try {
        auto str_ptr = prx->DoCommand(json_.dump());
        if (!str_ptr) {
          return StructT();
        }
        ret_struct.from_json(*str_ptr);
      } catch (std::exception &ex) {
        batch_cmd_vec_.clear();
        json_.clear();
        throw std::runtime_error(ex.what());
      }
      batch_cmd_vec_.clear();
      json_.clear();
    }
    if (cmd_) {
      try {
        auto str_ptr = prx->DoCommand(cmd_->get_json().dump());
        if (!str_ptr) {
          return StructT();
        }
        ret_struct.from_json(*str_ptr);
      } catch (std::exception &ex) {
        json_.clear();
        cmd_.reset();
        throw std::runtime_error(ex.what());
      }
      json_.clear();
      cmd_.reset();
    }
    return ret_struct;
  }
};
